﻿using Elasticsearch.Net;
using MyCompany.MyProject.Common.Helper;
using Newtonsoft.Json;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyCompany.MyProject.Common.LogHelper;

/// <summary>
/// 日志锁
/// </summary>
public class LogLock
{
    static ReaderWriterLockSlim LockWriteLock = new ReaderWriterLockSlim();
    static int WritedCount = 0;
    static int FailedCount = 0;
    static string _contentRoot = string.Empty;

    public LogLock(string contentRoot)
    {
        _contentRoot = contentRoot;
    }

    /// <summary>
    /// 输出日志到数据库和文件
    /// </summary>
    /// <param name="prefix"></param>
    /// <param name="traceId"></param>
    /// <param name="dataParas"></param>
    /// <param name="IsHeader"></param>
    public static void OutLogAOP(string prefix, string traceId, string[] dataParas, bool IsHeader = true)
    {
        string AppSettingNodeName = "AppSettings";
        string AppSettingName = "LogAOP";
        switch (prefix)
        {
            case "AOPLog": AppSettingName = "LogAOP"; break;
            case "AOPLogEx": AppSettingName = "LogAOP"; break;
            case "RequestIpInfoLog":
                AppSettingName = "IPLog";
                AppSettingNodeName = "Middleware";
                break;
            case "RecordAccessLogs":
                AppSettingNodeName = "RecordAccessLogs";
                AppSettingNodeName = "Middleware";
                break;
            case "SqlLog": AppSettingName = "SqlAOP"; break;
            case "RequestResponseLog":
                AppSettingNodeName = "Middleware";
                AppSettingNodeName = "RequestResponseLog";
                break;
            default:
                break;
        }

        if (AppSettings.app(AppSettingNodeName, AppSettingName, "Enabled").ObjToBool())
        {
            if (AppSettings.app(AppSettingNodeName, AppSettingName, "LogToDB", "Enabled").ObjToBool())
            {
                OutSqlToDB(prefix, traceId, dataParas, IsHeader);
            }
            if (AppSettings.app(AppSettingNodeName, AppSettingName, "LogToFile", "Enabled").ObjToBool())
            {
                OutSqlLogToFile(prefix, traceId, dataParas, IsHeader);
            }
        }
    }

    /// <summary>
    /// 输出SQL 日志到文件
    /// </summary>
    /// <param name="prefix"></param>
    /// <param name="traceId"></param>
    /// <param name="dataPars"></param>
    /// <param name="isHeader"></param>
    /// <param name="isWrite"></param>
    public static void OutSqlLogToFile(string prefix, string traceId, string[] dataPars, bool isHeader = true, bool isWrite = false)
    {
        try
        {
            /**
             * 设置读写锁为写入模式独占资源，其他写入请求需要等待本次写入结束之后才能继续写入
             * 注意：长时间持有读线程或写线程会使其他线程发生饥饿，为了得到最好的性能，需要考虑程序构建应用程序以将写访问持续的时间最短
             *      从性能方面考虑，请求进入写入模式应该紧跟文件操作之前，在此处进入写仅为了降低代码的复杂性
             *      因进入与退出写入模式应在同一个 try catch finally 语句块内，所以在进入写模式之前不能触发异常，否则释放次数大于请求此时会引发异常。
             */

            LockWriteLock.EnterWriteLock();

            var folderPath = Path.Combine(_contentRoot, "Log");
            if (!Directory.Exists(folderPath))
            {
                Directory.CreateDirectory(folderPath);
            }

            var logFilePath = FileHelper.GetAvailableFileWithPrefixOrderSize(folderPath, prefix);
            switch (prefix)
            {
                case "AOPLog":
                    AOPLogInfo apiLogAopInfo = JsonConvert.DeserializeObject<AOPLogInfo>(dataPars[1])!;
                    // 记录被拦截方法信息的日志信息
                    var dataIntercept = @$"
                                【操作时间】：{apiLogAopInfo!.RequestTime}\r\n
                                【当前操作用户】：{apiLogAopInfo!.OpUserName}\r\n
                                【当前执行方法】：{apiLogAopInfo!.RequestMethodName}\r\n
                                【携带的参数】：{apiLogAopInfo!.RequestParamsName}\r\n
                                【携带的参数Json】：{apiLogAopInfo!.RequestParamsData}\r\n
                                【响应时间】：{apiLogAopInfo!.ResponseIntervalTime}\r\n
                                【执行完成时间】：{apiLogAopInfo!.ResponseTime}\r\n
                                【执行完成结果】：{apiLogAopInfo!.ResponseJsonData}\r\n";
                    dataPars = new string[] { dataIntercept };
                    break;
                case "AOPLogEx":
                    AOPLogExInfo apiLogAopExInfo = JsonConvert.DeserializeObject<AOPLogExInfo>(dataPars[1])!;
                    var dataInterceptEx = "" +
                        $"【操作时间】：{apiLogAopExInfo.AOPLogInfo!.RequestTime}\\r\\n" +
                        $"【当前操作用户】：{apiLogAopExInfo.AOPLogInfo!.OpUserName}\\r\\n" +
                        $"【当前执行方法】：{apiLogAopExInfo.AOPLogInfo!.RequestMethodName}\\r\\n" +
                        $"【携带的参数有】：{apiLogAopExInfo.AOPLogInfo!.RequestParamsName}\\r\\n" +
                        $"【携带的参数JSON】：{apiLogAopExInfo.AOPLogInfo!.RequestParamsData}\\r\\n" +
                        $"【响应时间】：{apiLogAopExInfo.AOPLogInfo!.ResponseTime}\\r\\n" +
                        $"【执行完成时间】：{apiLogAopExInfo.AOPLogInfo!.ResponseTime}\\r\\n" +
                        $"【执行完成结果】：{apiLogAopExInfo.AOPLogInfo!.ResponseJsonData}\\r\\n" +
                        $"【执行完成异常信息】：方法中出现异常：{apiLogAopExInfo.ExMessage}\\r\\n" +
                        $"【执行完成异常】：方法中出现异常：{apiLogAopExInfo.InnerException}\\r\\n";
                    dataPars = new string[] { dataInterceptEx };
                    break;
            }

            var now = DateTime.Now;
            string logContent = string.Join("\r\n", dataPars);
            if (isHeader)
            {
                logContent = "--------------------------------\r\n" +
                        DateTime.Now + "|\r\n" +
                        String.Join("\r\n", dataPars) + "\r\n";
            }
            else
            {
                logContent = dataPars[1] + ",\r\n";
            }

            if (isWrite)
            {
                File.WriteAllText(logFilePath, logContent);
            }
            else
            {
                File.AppendAllText(logFilePath, logContent);
            }
            WritedCount++;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            FailedCount++;
        }
        finally
        {
            // 退出写入模式，释放资源占用
            LockWriteLock.ExitWriteLock();
        }
    }

    /// <summary>
    /// 输出日志到数据库
    /// </summary>
    /// <param name="prefix"></param>
    /// <param name="traceId"></param>
    /// <param name="dataPars"></param>
    /// <param name="isHeader"></param>
    /// <param name="isWrite"></param>
    public static void OutSqlToDB(string prefix, string traceId, string[] dataPars, bool isHeader = true, bool isWrite = false)
    {
        dataPars = dataPars.Skip(1).ToArray();
        string logContent = string.Join("", dataPars);
        if (isHeader)
        {
            logContent = string.Join("", dataPars);
        }
        switch (prefix)
        {
            case "AOPLog": Log.Information(logContent); break;
            case "AOPLogEx": Log.Error(logContent); break;
            case "RequestIpInfoLog": Log.Information(logContent); break;
            case "RecordAccessLogs": Log.Information(logContent); break;
            case "SqlLog": Log.Information(logContent); break;
            case "RequestResponseLog": Log.Information(logContent); break;
            default:
                break;
        }
    }

    public static List<LogInfo> GetLogData()
    {
        List<LogInfo> aopLogs = new List<LogInfo>();
        List<LogInfo> excLogs = new List<LogInfo>();
        List<LogInfo> sqlLogs = new List<LogInfo>();
        List<LogInfo> reqresLogs = new List<LogInfo>();

        try
        {
            var aoplogContent = ReadLog(Path.Combine(_contentRoot, "Log"), "AOPLog_", Encoding.UTF8, ReadType.Prefix);

            if (!string.IsNullOrEmpty(aoplogContent))
            {
                aopLogs = aoplogContent.Split("--------------------------------")
                    .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n")
                    .Select(d => new LogInfo
                    {
                        Datetime = d.Split("|")[0].ObjToDate(),
                        Content = d.Split("|")[1]?.Replace("\r\n", "<br>"),
                        LogColor = "AOP",
                    }).ToList();
            }
        }
        catch (Exception)
        {
        }

        try
        {
            var exclogContent = ReadLog(Path.Combine(_contentRoot, "Log"), $"GlobalExceptionLogs_{DateTime.Now.ToString("yyyMMdd")}.log", Encoding.UTF8);

            if (!string.IsNullOrEmpty(exclogContent))
            {
                excLogs = exclogContent.Split("--------------------------------")
                    .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n")
                    .Select(d => new LogInfo
                    {
                        Datetime = (d.Split("|")[0]).Split(',')[0].ObjToDate(),
                        Content = d.Split("|")[1]?.Replace("\r\n", "<br>"),
                        LogColor = "EXC",
                        Import = 9,
                    }).ToList();
            }
        }
        catch (Exception)
        {
        }


        try
        {
            var sqllogContent = ReadLog(Path.Combine(_contentRoot, "Log"), "SqlLog_", Encoding.UTF8, ReadType.PrefixLatest);

            if (!string.IsNullOrEmpty(sqllogContent))
            {
                sqlLogs = sqllogContent.Split("--------------------------------")
                    .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n")
                    .Select(d => new LogInfo
                    {
                        Datetime = d.Split("|")[0].ObjToDate(),
                        Content = d.Split("|")[1]?.Replace("\r\n", "<br>"),
                        LogColor = "SQL",
                    }).ToList();
            }
        }
        catch (Exception)
        {
        }

        //try
        //{
        //    reqresLogs = ReadLog(Path.Combine(_contentRoot, "Log", "RequestResponseLog.log"), Encoding.UTF8)?
        //          .Split("--------------------------------")
        //          .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n")
        //          .Select(d => new LogInfo
        //          {
        //              Datetime = d.Split("|")[0].ObjToDate(),
        //              Content = d.Split("|")[1]?.Replace("\r\n", "<br>"),
        //              LogColor = "ReqRes",
        //          }).ToList();
        //}
        //catch (Exception)
        //{
        //}

        try
        {
            var Logs = GetRequestInfo(ReadType.PrefixLatest);

            Logs = Logs.Where(d => d.Datetime.ObjToDate() >= DateTime.Today).ToList();

            reqresLogs = Logs.Select(d => new LogInfo
            {
                Datetime = d.Datetime.ObjToDate(),
                Content = $"IP:{d.Ip}<br>{d.Url}",
                LogColor = "ReqRes",
            }).ToList();
        }
        catch (Exception)
        {
        }

        if (excLogs != null)
        {
            aopLogs.AddRange(excLogs);
        }

        if (sqlLogs != null)
        {
            aopLogs.AddRange(sqlLogs);
        }

        if (reqresLogs != null)
        {
            aopLogs.AddRange(reqresLogs);
        }

        aopLogs = aopLogs.OrderByDescending(d => d.Import).ThenByDescending(d => d.Datetime).Take(100).ToList();

        return aopLogs;
    }



    private static List<RequestInfo> GetRequestInfo(ReadType readType)
    {
        List<RequestInfo> requestInfos = new();
        var accessLogs = ReadLog(Path.Combine(_contentRoot, "Log"), "RequestIpInfoLog_", Encoding.UTF8, readType).ObjToString();
        try
        {
            return JsonConvert.DeserializeObject<List<RequestInfo>>("[" + accessLogs + "]");
        }
        catch (Exception)
        {
            var accLogArr = accessLogs.Split("\r\n");
            foreach (var item in accLogArr)
            {
                if (item.ObjToString() != "")
                {
                    try
                    {
                        var accItem = JsonConvert.DeserializeObject<RequestInfo>(item.TrimEnd(','));
                        requestInfos.Add(accItem);
                    }
                    catch (Exception)
                    {
                    }
                }
            }
        }

        return requestInfos;
    }

    /// <summary>
    /// 读取文件内容
    /// </summary>
    /// <param name="folderPath">文件夹路径</param>
    /// <param name="fileName">文件名</param>
    /// <param name="encode">编码</param>
    /// <param name="readType">读取类型(0:精准,1:前缀模糊)</param>
    /// <returns></returns>
    public static string ReadLog(string folderPath, string fileName, Encoding encode, ReadType readType = ReadType.Accurate, int takeOnlyTop = -1)
    {
        string s = "";
        try
        {
            LockWriteLock.EnterReadLock();

            // 根据文件名读取当前文件内容
            if (readType == ReadType.Accurate)
            {
                var filePath = Path.Combine(folderPath, fileName);
                if (!File.Exists(filePath))
                {
                    s = null;
                }
                else
                {
                    StreamReader f2 = new StreamReader(filePath, encode);
                    s = f2.ReadToEnd();
                    f2.Close();
                    f2.Dispose();
                }
            }

            // 根据前缀读取所有文件内容
            if (readType == ReadType.Prefix)
            {
                var allFiles = new DirectoryInfo(folderPath);
                var selectFiles = allFiles.GetFiles().Where(fi => fi.Name.ToLower().Contains(fileName.ToLower())).ToList();

                selectFiles = takeOnlyTop > 0 ? selectFiles.OrderByDescending(d => d.Name).Take(takeOnlyTop).ToList() : selectFiles;

                foreach (var item in selectFiles)
                {
                    if (File.Exists(item.FullName))
                    {
                        StreamReader f2 = new StreamReader(item.FullName, encode);
                        s += f2.ReadToEnd();
                        f2.Close();
                        f2.Dispose();
                    }
                }
            }

            // 根据前缀读取 最新文件 时间倒叙
            if (readType == ReadType.PrefixLatest)
            {
                var allFiles = new DirectoryInfo(folderPath);
                var selectLastestFile = allFiles.GetFiles().Where(fi => fi.Name.ToLower().Contains(fileName.ToLower())).OrderByDescending(d => d.Name).FirstOrDefault();

                if (selectLastestFile != null && File.Exists(selectLastestFile.FullName))
                {
                    StreamReader f2 = new StreamReader(selectLastestFile.FullName, encode);
                    s = f2.ReadToEnd();
                    f2.Close();
                    f2.Dispose();
                }
            }
        }
        catch (Exception)
        {
            FailedCount++;
        }
        finally
        {
            LockWriteLock.ExitReadLock();
        }

        return s;
    }

}

public enum ReadType
{
    /// <summary>
    /// 精确查找一个
    /// </summary>
    Accurate,

    /// <summary>
    /// 指定前缀，模糊查找全部
    /// </summary>
    Prefix,

    /// <summary>
    /// 指定前缀，最新一个文件
    /// </summary>
    PrefixLatest
}
